package com.edu.juc.jmm.volatiles;

import java.util.concurrent.TimeUnit;

/**
 * volatile 两大特点：
 * 1.可见性
 * 2.有序性 ---> 禁重排
 * <p>
 * 当写一个volatile变量时，JVM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
 * 当读一个volatile变量时，JVM会先在主内存中查找该变量，如果主内存中存在该变量，则直接从主内存中获取变量值，
 * 所以volatile的写内存语义是直接刷新到主内存中，而读内存语义是直接从主内存中获取变量值。
 * <p>
 * 底层原理：
 * 内存屏障（同步屏障指令，是CPU或编译器在对内存随机访问的操作中的一个同步点，
 * 使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作），避免代码重排序。
 * 内存屏障其实就是有一种JVM指令，Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令，
 * 通过这些内存屏障指令，volatile实现了Java内存模型中的可见性和有序性（禁重排），但volatile无法保证原子性。
 * 1.内存屏障之前的所有写操作都要写回到主内存，
 * 2.内存屏障之前的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。
 * <p>
 * 写屏障(Store Memory Barrier)：告诉处理器在写屏障之前将所有存储在缓存(store buffer)中的数据同步到主内存中。
 * 也就是说，当看到Store屏障指令，就必须把该指令之前的所有写入指令执行完毕才能继续往下执行。
 * <p>
 * 读屏障(Load Memory Barrier)：告诉处理器在读屏障之后的读操作，都在读屏障之后执行。
 * 也就是说，Load屏障指令之后就能够保证后面的读取数据指令一定能够读取到最新的数据。
 * <p>
 * 重排序时，不允许把内存屏障之后的指令重排序到内存屏障之前。
 * 即：对volatile变量的写，先行发生于任意后续对这个volatile变量的读，也叫写后读。
 * <p>
 * 1.在每一个 volatile 写操作前面插入一个 StoreStore 屏障
 * - StoreStore 屏障可以保证在 volatile 写之前，其前面的所有普通写操作都已经刷新到主内存。
 * 2.在每一个 volatile 写操作后面插入一个 StoreLoad 屏障
 * - StoreLoad 屏障的作用是避免 volatile 写与后面可能有的 volatile 读/写操作重排序。
 * 3.在每一个 volatile 读操作后面插入一个 LoadLoad 屏障
 * - LoadLoad 屏障用来禁止处理器把上面的 volatile 读与下面的普通读重排序。
 * 4.在每一个 volatile 读操作后面插入一个 LoadStore 屏障
 * - LoadStore 屏障用来禁止处理器把上面的 volatile 读与下面的普通写重排序。
 */
public class VolatileTest {
    static volatile boolean flag = true;
//    static volatile int i = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            long start = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":: start...");
            while (flag) {
//                i++;
            }
            System.out.println(Thread.currentThread().getName() + ":: end.");
            long end = System.currentTimeMillis();
            System.out.println(end - start);

        }, "t1").start();

        TimeUnit.SECONDS.sleep(1);

        // 不加volatile flag修改对t1不可见
        flag = false;

        System.out.println(Thread.currentThread().getName() + ":: 修改完成");
    }

}
